package cn.xinfei.xdecision.engine.runner.executor.handler;

import cn.xinfei.xdecision.common.model.component.guiderule.RuleInfo;
import cn.xinfei.xdecision.common.model.component.guiderule.vo.RuleBlockVo;
import cn.xinfei.xdecision.common.model.component.guiderule.vo.RuleConditionVo;
import cn.xinfei.xdecision.common.model.component.guiderule.vo.RuleVersionVo;
import cn.xinfei.xdecision.common.model.component.scriptrule.RuleScriptVersion;
import cn.xinfei.xdecision.common.model.component.strategyout.StrategyOutput;
import cn.xinfei.xdecision.common.model.enginex.model.RuleHitModel;
import cn.xinfei.xdecision.common.utils.constant.strategyx.RuleConst;
import cn.xinfei.xdecision.common.utils.util.JsonUtils;
import cn.xinfei.xdecision.common.utils.util.runner.jeval.EvaluationException;
import cn.xinfei.xdecision.common.vo.FieldInfoVo;
import cn.xinfei.xdecision.engine.runner.context.InternalVariableConstant;
import cn.xinfei.xdecision.engine.runner.context.PipelineContextHolder;
import cn.xinfei.xdecision.engine.runner.executor.NodeHandler;
import cn.xinfei.xdecision.engine.runner.executor.model.StateTransmitModel;
import cn.xinfei.xdecision.engine.runner.executor.strategy.IEngineRunner;
import cn.xinfei.xdecision.engine.runner.expressions.RuleConditionExecutor;
import cn.xinfei.xdecision.engine.runner.expressions.ScriptExecutor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RuleGroupNodeHandler {

    @Autowired
    @Qualifier("bizProcessorThreadPool")
    public ExecutorService bizProcessorThreadPool;

    @Autowired
    private NodeOutputHandler nodeOutputHandler;


    @Value("${xdecision.timeout.parallelRuleThreadWaitingTime:20}")
    private int parallelRuleThreadWaitingTime;


    /**
     * 串行执行规则
     *
     * @param
     * @param ruleInfoList
     * @param
     * @return
     */
    public StateTransmitModel serialRule(NodeHandler nodeHandler, StateTransmitModel stateTransmitModel, String nodeCode, List<RuleInfo> ruleInfoList) throws Exception {
        log.info("串行执行规则,nodeCode={}", nodeCode);

        for (int i = 0; i < ruleInfoList.size(); i++) {
            RuleInfo rule = ruleInfoList.get(i);
            long start = System.currentTimeMillis();
            Set<String> fieldCodes = getFieldCodes(nodeHandler, rule);
            String ruleCode = rule.getCode();
            IEngineRunner engineNodeRunner = nodeHandler.getEngineNodeRunner();
            engineNodeRunner.getNodeFieldValue(fieldCodes, ruleCode);
            boolean hitFlag = executeByDifficulty(stateTransmitModel, nodeCode, rule);
            long end = System.currentTimeMillis();
            log.info("串行规则命中，ruleCode={},hitFlag={},ruleDuration={}", rule.getCode(), hitFlag, end - start);
            if (hitFlag) {
                break;
            }
        }
        return stateTransmitModel;
    }


    /**
     * 并行执行规则
     *
     * @param ruleInfoList
     * @param
     * @return
     */
    public StateTransmitModel parallelRule(NodeHandler nodeHandler, StateTransmitModel stateTransmitModel, String nodeCode, List<RuleInfo> ruleInfoList) throws Exception {
        log.info("并行规则开始,nodeCode={}", nodeCode);
        if (CollectionUtils.isEmpty(ruleInfoList)) {
            return stateTransmitModel;
        }

        Set<String> fieldCodes = new HashSet<>();
        for (int i = 0; i < ruleInfoList.size(); i++) {
            RuleInfo rule = ruleInfoList.get(i);
            fieldCodes.addAll(getFieldCodes(nodeHandler, rule));
        }
        IEngineRunner engineNodeRunner = nodeHandler.getEngineNodeRunner();
        engineNodeRunner.getNodeFieldValue(fieldCodes, nodeCode);

        CountDownLatch countDownLatch = new CountDownLatch(ruleInfoList.size());
        for (int i = 0; i < ruleInfoList.size(); i++) {
            RuleInfo rule = ruleInfoList.get(i);
            CompletableFuture.runAsync(() -> {
                try {
                    long start = System.currentTimeMillis();
                    log.info("并行规则命中，ruleCode={}", rule.getCode());
                    boolean hitFlag = executeByDifficulty( stateTransmitModel, nodeCode, rule);
                    long end = System.currentTimeMillis();
                    log.info("并行规则命中，ruleCode={},hitFlag={},ruleDuration={}", rule.getCode(), hitFlag, end - start);
                } catch (Exception e) {
                    log.error("并行规则异常，rule={}", JsonUtils.toJsonString(rule), e);
                } finally {
                    countDownLatch.countDown();
                }
            }, bizProcessorThreadPool);
        }
        try {
            //是否需要设置执行超时等待时间
//            countDownLatch.await(10000L, TimeUnit.SECONDS);
            countDownLatch.await(parallelRuleThreadWaitingTime, TimeUnit.SECONDS);
            log.info("并行规则结束,nodeCode={}", nodeCode);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("并行规则异常,nodeCode={}", nodeCode);
            //todo shiyusen 埋点监控
        }

        return stateTransmitModel;
    }


    /**
     * 根究规则类型选择执行
     *
     * @param rule
     * @param
     * @return
     */
    public boolean executeByDifficulty( StateTransmitModel stateTransmitModel, String nodeCode, RuleInfo rule) throws Exception {


        if (rule.getDifficulty() == 2) {
            return executeComplexRule(stateTransmitModel, nodeCode, rule);
        }
        if (rule.getDifficulty() == 3) {
            return executeScriptRule(stateTransmitModel, nodeCode, rule);
        }
        return false;
    }

    /**
     * 规则的变量预加载，只加载规则相关的，输出相关的暂不加载（与策略输出保持一只，即命中后确定输出）
     *
     * @param nodeHandler
     * @return
     */
    private Set<String> getFieldCodes(NodeHandler nodeHandler, RuleInfo rule) {
        Map<String, FieldInfoVo> inputFields = nodeHandler.getInputFields();
        Set<String> collect = inputFields.entrySet().stream().filter(entry -> {
            String ruleCode = entry.getValue().getRuleCode();
            if (StringUtils.isNotEmpty(ruleCode) && ruleCode.equals(rule.getCode())) {
                return true;
            }
            return false;
        }).map(entry -> entry.getValue().getFieldCode()).collect(Collectors.toSet());
        return collect;
    }


    /**
     * 执行复杂规则
     *
     * @param rule
     * @param
     * @return
     */
    public boolean executeComplexRule(StateTransmitModel stateTransmitModel, String nodeCode, RuleInfo rule) throws EvaluationException {

        log.info("进入复杂规则组，{}", rule.toSimplyString());
        RuleVersionVo ruleVersion = rule.getVersion();
        if (ruleVersion == null) {
            return false;
        }
        // 循环执行规则块，按顺序命中第一个块就结束
        List<RuleBlockVo> ruleBlockVoList = ruleVersion.getRuleBlockVoList();
        for (RuleBlockVo ruleBlockVo : ruleBlockVoList) {
            //获取规则需要执行的condition逻辑。
            RuleConditionVo ruleCondition = ruleBlockVo.getRuleConditionVo();
            long start = System.currentTimeMillis();
            //传入输入参数、中间变量、输出参数和需要执行的condition逻辑获取执行结果
            boolean hitFlag = RuleConditionExecutor.executeRuleCondition(nodeCode, ruleCondition);
            long end = System.currentTimeMillis();
            log.info("rule block hit hitFlag={},ruleCode={},ruleId={},versionId={},blockId={},blockDuration={}", hitFlag, rule.getCode(), ruleCondition.getRuleId(), ruleCondition.getVersionId(), ruleCondition.getBlockId(), end - start);

            String ruleHitResult = "miss";
            String resultFieldEn = ruleBlockVo.getResultFieldCode();

            RuleHitModel blockRuleInfo = new RuleHitModel(false);
            blockRuleInfo.setNodeCode(nodeCode);
            blockRuleInfo.setRuleId(rule.getId());
            blockRuleInfo.setRuleCode(rule.getCode());
            blockRuleInfo.setRuleVersionId(rule.getVersionId());
            blockRuleInfo.setVersionNo(rule.getVersionNo());
            blockRuleInfo.setRuleName(rule.getName());
            blockRuleInfo.setBlockId(ruleBlockVo.getId());

            if (hitFlag) {
                log.debug("规则块命中输出处理");
                ruleHitResult = "hit";
                String scoreFieldEn = ruleBlockVo.getScoreFieldCode();
                Long ruleBlockVoScore = ruleBlockVo.getScore();
                blockRuleInfo.setRuleHitFlag(true);
                blockRuleInfo.setRuleScore(ruleBlockVoScore);

                PipelineContextHolder.putInContext(nodeCode, resultFieldEn, ruleHitResult);
                PipelineContextHolder.putInContext(nodeCode, scoreFieldEn, ruleBlockVoScore);
                List<StrategyOutput> strategyOutputList = ruleBlockVo.getStrategyOutputList();
                //固定输出
                stateTransmitModel.getNodeOutput().put(resultFieldEn, ruleHitResult);
                stateTransmitModel.getNodeOutput().put(scoreFieldEn, ruleBlockVoScore);
                //自定义命中输出
                Map<String, Object> output = nodeOutputHandler.setOutput(nodeCode, strategyOutputList);
                stateTransmitModel.getNodeOutput().putAll(output);
                stateTransmitModel.getHitResultList().add(blockRuleInfo);
                PipelineContextHolder.getHitResultList().add(blockRuleInfo);
                return true;
            }
            log.debug("规则块未命中输出处理");
            stateTransmitModel.getHitResultList().add(blockRuleInfo);
            PipelineContextHolder.putInContext(nodeCode, resultFieldEn, ruleHitResult);
            stateTransmitModel.getNodeOutput().put(resultFieldEn, ruleHitResult);
            List<StrategyOutput> failOutputList = ruleBlockVo.getFailOutputList();
            //自定义命中输出
            Map<String, Object> output = nodeOutputHandler.setOutput(nodeCode, failOutputList);
            stateTransmitModel.getNodeOutput().putAll(output);
        }
        return false;
    }


    /**
     * 执行脚本规则
     *
     * @param rule
     * @param
     * @return
     */
    public boolean executeScriptRule(StateTransmitModel stateTransmitModel, String nodeCode, RuleInfo rule) throws EvaluationException {
        boolean hitFlag = false;

        log.info("进入脚本规则组，nodeCode={},{}", nodeCode, rule.toSimplyString());
        RuleScriptVersion scriptVersion = rule.getScriptVersion();
        if (RuleConst.ScriptType.GROOVY.equals(rule.getScriptType()) && RuleConst.ScriptType.GROOVY.equals(scriptVersion.getScriptType())) {
            //groovy脚本执行
            //取出需要执行的版本
            if (scriptVersion == null || StringUtils.isBlank(scriptVersion.getScriptContent())) {
                return false;
            }
            //取出脚本内容
            String scriptContent = scriptVersion.getScriptContent();
            //取出本规则集的规则列表
            RuleHitModel resultInfoVo = new RuleHitModel();
            resultInfoVo.setVersionNo(rule.getVersionNo());
            resultInfoVo.setRuleCode(rule.getCode());
            resultInfoVo.setRuleName(rule.getName());
            resultInfoVo.setRuleVersionId(scriptVersion.getId());
            resultInfoVo.setRuleId(rule.getId());

            try {
                JSONObject formulaJson = JSON.parseObject(scriptContent);
                Map<String, FieldInfoVo> fieldInfo = extractFieldInfo(formulaJson);
                Object resp = ScriptExecutor.executeScript(formulaJson, fieldInfo);
                Long ruleScore = 0l;
                if (resp instanceof HashMap) {
                    Map resultMap = (HashMap) resp;
                    JSONObject executeResult = JSON.parseObject(JSON.toJSONString(resultMap));
                    ruleScore = executeResult.getLongValue(InternalVariableConstant.RULE_HIT_SCORE.getCode());
                    String result = executeResult.getString(InternalVariableConstant.RULE_HIT_RESULT.getCode());
                    JSONObject innerContext;
                    if (executeResult.containsKey("context")) {
                        innerContext = executeResult.getJSONObject("context");
                    } else {
                        innerContext = executeResult.getJSONObject("updateInputMap");
                    }

                    if (executeResult != null) {
                        resultInfoVo.setRuleScore(ruleScore);
                    }
                    if ("hit".equals(result)) {
                        hitFlag = true;
                        resultInfoVo.setRuleHitFlag(true);
                        PipelineContextHolder.getHitResultList().add(resultInfoVo);
                    }
                    stateTransmitModel.getHitResultList().add(resultInfoVo);
                    stateTransmitModel.getNodeOutput().put(InternalVariableConstant.RULE_HIT_SCORE.getCode(), ruleScore);
                    stateTransmitModel.getNodeOutput().put(InternalVariableConstant.RULE_HIT_RESULT.getCode(), result);
                    PipelineContextHolder.putInContext(nodeCode, InternalVariableConstant.RULE_HIT_SCORE.getCode(), ruleScore);
                    PipelineContextHolder.putInContext(nodeCode, InternalVariableConstant.RULE_HIT_RESULT.getCode(), result);
                    //更新入参
                    if (innerContext != null && !innerContext.isEmpty()) {
                        Set<Map.Entry<String, Object>> entries = ScriptExecutor.handleGroovyResult(innerContext).entrySet();
                        for (Map.Entry<String, Object> entry : entries) {
                            PipelineContextHolder.putInContext(nodeCode, entry.getKey(), entry.getValue());
                            stateTransmitModel.getNodeOutput().put(entry.getKey(), entry.getValue());
                        }
                    }
                }
            } catch (Exception e) {
                log.error("脚本规则集执行错误：{}", e.getMessage(), e);
                throw e;
            }
        }
        return hitFlag;
    }

    //根据入参map,通过公式和groovy计算出返回结果
    private Map<String, FieldInfoVo> extractFieldInfo(JSONObject scriptContent) {
        //找到脚本中引用的字段,放入data中
        JSONArray farr = scriptContent.getJSONArray("farr");
        //字段cn为key，字段en为value
        Map<String, FieldInfoVo> fieldInfos = new HashMap<>();
        if (farr != null && !"".equals(farr)) {
            farr.forEach(fieldInfo -> {
                FieldInfoVo field = JSON.parseObject(JsonUtils.toJsonString(fieldInfo), FieldInfoVo.class);
                String fieldCn = field.getFieldName();
                String fieldEn = field.getFieldCode();
                if (fieldCn != null && fieldEn != null && !"".equals(fieldCn) && !"".equals(fieldEn)) {
                    fieldInfos.put(fieldCn, field);
                    fieldInfos.put(fieldEn, field);
                }
            });
        }
        return fieldInfos;
    }
}
